Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/webhooks/webhook_preview/app/[uuid]/page.tsx
640 views
1
"use client";
2
3
import { useState, useEffect } from "react";
4
import { Header } from "@/components/header";
5
import { Layout } from "@/components/layout";
6
import { LicensePlateSidebar } from "@/components/license-plate-sidebar";
7
import { LastVehicleDetails } from "@/components/last-vehicle-details";
8
import { DashboardSummary } from "@/components/dashboard-summary";
9
import { EmptyState } from "@/components/empty-state";
10
import type { WebhookData } from "@/types/webhook";
11
import { useToast } from "@/hooks/use-toast";
12
import { useParams, useRouter } from "next/navigation";
13
import { Copy, Trash } from "lucide-react";
14
15
export default function WebhookDataPage() {
16
const params = useParams();
17
const uuid = params?.uuid as string;
18
19
const [webhookData, setWebhookData] = useState<WebhookData[]>([]);
20
const [loading, setLoading] = useState(true);
21
const [searchTerm, setSearchTerm] = useState("");
22
const { toast } = useToast();
23
const router = useRouter();
24
const [baseUrl, setBaseUrl] = useState("");
25
26
// Set base URL on client side
27
useEffect(() => {
28
setBaseUrl(window.location.origin);
29
}, []);
30
31
// Fetch data on page load
32
useEffect(() => {
33
if (!uuid) return;
34
35
const fetchData = async () => {
36
try {
37
setLoading(true);
38
const url = `/api/webhook/${uuid}`;
39
const response = await fetch(url);
40
41
if (!response.ok) {
42
if (response.status === 404) {
43
toast({
44
title: "Webhook Not Found",
45
description: "The webhook you're looking for doesn't exist",
46
variant: "destructive",
47
});
48
router.push("/");
49
return;
50
} else if (response.status === 400) {
51
toast({
52
title: "Invalid UUID",
53
description: "The UUID you provided is invalid",
54
variant: "destructive",
55
});
56
router.push("/");
57
return;
58
}
59
60
toast({
61
title: "Error",
62
description: `Failed to fetch data: ${response.statusText}`,
63
variant: "destructive",
64
});
65
return;
66
}
67
68
const data = await response.json();
69
if (data.length >= process.env.NEXT_PUBLIC_MAX_WEBHOOK_REQUESTS) {
70
toast({
71
title: "Webhook Limit Reached",
72
description:
73
"You have reached the maximum number of requests for this webhook",
74
variant: "destructive",
75
});
76
}
77
78
if (Array.isArray(data) && data.length > 0) {
79
console.log("Received data:", data.length, "items");
80
setWebhookData(data);
81
} else {
82
setWebhookData([]);
83
}
84
} catch (error) {
85
console.error("Error fetching webhook data:", error);
86
toast({
87
title: "Error",
88
description: "Failed to fetch webhook data",
89
variant: "destructive",
90
});
91
} finally {
92
setLoading(false);
93
}
94
};
95
96
// Fetch initial data
97
fetchData();
98
99
// Set up polling for real-time updates
100
const intervalId = setInterval(fetchData, 5000);
101
102
return () => clearInterval(intervalId);
103
}, [uuid, toast, router]);
104
105
const handleRefresh = async () => {
106
setLoading(true);
107
try {
108
const response = await fetch(`/api/webhook/${uuid}`);
109
if (response.ok) {
110
const data = await response.json();
111
setWebhookData(data);
112
toast({
113
title: "Data Refreshed",
114
description: `Found ${data.length} records`,
115
});
116
}
117
} catch (error) {
118
console.error("Error refreshing data:", error);
119
toast({
120
title: "Refresh Failed",
121
description: "Could not refresh data",
122
variant: "destructive",
123
});
124
} finally {
125
setLoading(false);
126
}
127
};
128
129
const handleDataClear = async () => {
130
try {
131
setLoading(true);
132
const response = await fetch(`/api/webhook/${uuid}`, {
133
method: "DELETE",
134
});
135
136
if (response.ok) {
137
setWebhookData([]);
138
toast({
139
title: "Data Cleared",
140
description: "All webhook data has been cleared",
141
});
142
} else {
143
toast({
144
title: "Error",
145
description: "Failed to clear data",
146
variant: "destructive",
147
});
148
}
149
} catch (error) {
150
console.error("Error clearing data:", error);
151
toast({
152
title: "Error",
153
description: "Failed to clear data",
154
variant: "destructive",
155
});
156
} finally {
157
setLoading(false);
158
}
159
};
160
161
const testApiConnection = async () => {
162
try {
163
setLoading(true);
164
165
// First test a simple endpoint
166
const testResponse = await fetch("/api/webhook/test", {
167
method: "POST",
168
headers: {
169
"Content-Type": "application/json",
170
},
171
body: JSON.stringify({ test: "data" }),
172
});
173
174
if (testResponse.ok) {
175
// toast({
176
// title: "Test API Connection Successful",
177
// description: "The API is responding correctly",
178
// });
179
180
// Now test sending some sample data to our webhook
181
const sampleData = {
182
data: {
183
camera_id: "test-camera",
184
filename: "test.jpg",
185
results: [
186
{
187
plate: "ABC1D23",
188
box: { xmin: 0, ymin: 0, xmax: 100, ymax: 50 },
189
vehicle: {
190
type: "Car",
191
box: { xmin: 0, ymin: 0, xmax: 200, ymax: 150 },
192
score: 0.95,
193
},
194
color: [{ color: "black", score: 0.9 }],
195
model_make: [{ make: "Test", model: "Model", score: 0.8 }],
196
direction: 90,
197
speed: 60,
198
score: 0.95,
199
source_url: "test-url",
200
},
201
],
202
timestamp: new Date().toISOString(),
203
timestamp_local: new Date().toISOString(),
204
timestamp_camera: new Date().toISOString(),
205
},
206
hook: {
207
event: "test",
208
filename: "test.jpg",
209
id: "test-id",
210
target: `/api/webhook/${uuid}`,
211
},
212
receivedAt: new Date().toISOString(),
213
};
214
215
const webhookResponse = await fetch(`/api/webhook/${uuid}`, {
216
method: "POST",
217
headers: {
218
"Content-Type": "application/json",
219
},
220
body: JSON.stringify(sampleData),
221
});
222
223
if (webhookResponse.ok) {
224
toast({
225
title: "Test Data Sent Successfully",
226
description:
227
"Sample license plate data has been sent to your webhook",
228
});
229
230
// Refresh to show the test data
231
handleRefresh();
232
}
233
} else {
234
toast({
235
title: "API Connection Failed",
236
description: `Status: ${testResponse.status} ${testResponse.statusText}`,
237
variant: "destructive",
238
});
239
}
240
} catch (error) {
241
console.error("Error testing API connection:", error);
242
toast({
243
title: "API Connection Error",
244
description: error instanceof Error ? error.message : "Unknown error",
245
variant: "destructive",
246
});
247
} finally {
248
setLoading(false);
249
}
250
};
251
252
// Get the latest vehicle data
253
const latestVehicle =
254
webhookData.length > 0
255
? webhookData.sort((a, b) => {
256
const timeA = a.receivedAt ? new Date(a.receivedAt).getTime() : 0;
257
const timeB = b.receivedAt ? new Date(b.receivedAt).getTime() : 0;
258
return timeB - timeA;
259
})[0]
260
: null;
261
262
return (
263
<Layout
264
header={<Header />}
265
sidebar={
266
<LicensePlateSidebar
267
data={webhookData}
268
searchTerm={searchTerm}
269
onSearchChange={setSearchTerm}
270
/>
271
}
272
main={
273
<div className="space-y-4 overflow-auto">
274
{/* Dashboard Summary at the top */}
275
<DashboardSummary
276
data={webhookData}
277
onRefresh={handleRefresh}
278
onTestData={testApiConnection}
279
loading={loading}
280
/>
281
282
{/* Webhook URL Display */}
283
<div className="bg-white p-4 rounded-lg shadow">
284
<h2 className="text-lg font-semibold mb-2">Webhook URL</h2>
285
<div className="flex items-center gap-2">
286
<div className="bg-gray-100 h-10 px-3 flex items-center rounded flex-1 font-mono text-sm overflow-x-auto">
287
{baseUrl ? `${baseUrl}/api/webhook/${uuid}` : `Loading...`}
288
</div>
289
<button
290
onClick={() => {
291
if (baseUrl) {
292
navigator.clipboard.writeText(
293
`${baseUrl}/api/webhook/${uuid}`,
294
);
295
toast({
296
title: "Copy!",
297
description:
298
"URL of the webhook copied to the clipboard.",
299
});
300
}
301
}}
302
className="h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
303
title="Copiar URL"
304
>
305
<Copy className="w-4 h-4" />
306
</button>
307
<button
308
onClick={handleDataClear}
309
className="h-10 w-10 flex items-center justify-center bg-red-100 text-red-600 rounded hover:bg-red-200 transition-colors"
310
title="Limpar Dados"
311
>
312
<Trash className="w-4 h-4" />
313
</button>
314
</div>
315
</div>
316
317
{/* Main content area */}
318
{loading && webhookData.length === 0 ? (
319
<div className="flex items-center justify-center p-8">
320
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
321
<span className="ml-2">Loading data...</span>
322
</div>
323
) : webhookData.length === 0 ? (
324
<EmptyState uuid={uuid} />
325
) : (
326
<div className="space-y-4">
327
{/* Latest vehicle details */}
328
{latestVehicle && <LastVehicleDetails data={latestVehicle} />}
329
</div>
330
)}
331
</div>
332
}
333
/>
334
);
335
}
336
337